/*-------------------------------------------------------------------------
 *
 * plancache.h
 *	  计划缓存定义。
 *
 * 请参见plancache.c中的注释。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/utils/plancache.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef PLANCACHE_H
#define PLANCACHE_H

#include "access/tupdesc.h"
#include "lib/ilist.h"
#include "nodes/params.h"
#include "tcop/cmdtag.h"
#include "utils/queryenvironment.h"
#include "utils/resowner.h"


/* 前向声明，以避免在此处包含 parsenodes.h */
struct RawStmt;

/* plan_cache_mode的可能值 */
typedef enum
{
	PLAN_CACHE_MODE_AUTO,
	PLAN_CACHE_MODE_FORCE_GENERIC_PLAN,
	PLAN_CACHE_MODE_FORCE_CUSTOM_PLAN
}			PlanCacheMode;

/* GUC 参数 */
extern PGDLLIMPORT int plan_cache_mode;

#define CACHEDPLANSOURCE_MAGIC		195726186
#define CACHEDPLAN_MAGIC			953717834
#define CACHEDEXPR_MAGIC			838275847

/*
 * CachedPlanSource（或许更合适称为 CachedQuery）
 * 表示一个我们期望多次使用的 SQL 查询。它存储
 * 查询源文本、原始解析树，以及分析和重写的
 * 查询树，以及附加数据。缓存失效可能发生在
 * DDL 改变了查询所用对象的情况下。在这种情况下，我们会丢弃
 * 分析和重写的查询树，并在下次需要时重新构建它。
 *
 * 实际执行计划，由 CachedPlan 表示，是从
 * CachedPlanSource 派生的，当我们需要执行查询时。计划可以是
 * 通用的（可以与任何计划参数集一起使用）或自定义的（针对特定
 * 参数集）。plancache.c 包含决定
 * 在任何特定执行中采用哪种方式的逻辑。如果我们使用的是通用
 * 缓存计划，那么它旨在被多个执行共享，因此
 * 调用者必须始终将 CachedPlans 视为只读。
 *
 * 一旦成功构建并“保存”，CachedPlanSources 通常在
 * 后端的生命周期内存在，尽管可以显式丢弃它们。
 * CachedPlans 是引用计数的，当最后一个
 * 引用丢弃时，它们会自动消失。一个 CachedPlan 可以比它
 * 所创建的 CachedPlanSource 生命周期更长。
 *
 * 一个“未保存”的 CachedPlanSource 可以用于生成计划，但它
 * 存储在瞬态存储中，并且不会响应 sinval
 * 事件进行更新。
 *
 * 从保存的 CachedPlanSources 创建的 CachedPlans 也是在
 * 持久存储中，因此为了避免内存泄露，对它们的引用计数
 * 必须保存在持久数据结构或资源拥有者中。 
 * 从未保存的 CachedPlanSources 创建的 CachedPlans 存储在调用者
 * 的内存上下文的子级中，因此对它们的引用不应比
 * 该上下文的生命周期更长。（在这种情况下，引用计数在形式上
 * 是有些形式化的，尽管如果 CachedPlan 可以被提早丢弃，它可能是
 * 有用的。）
 *
 * 一个 CachedPlanSource 有两个相关的内存上下文：一个
 * 保存结构本身、查询源文本和原始解析树，另一个
 * 上下文保存重写的查询树和相关数据。这
 * 使得查询树在失效时可以轻松丢弃。
 *
 * 一些调用者希望即使是那种无需保存的一次性查询
 * 也使用 CachedPlan API。因此我们支持一种不
 * 进行数据复制或失效检查的“一次性”变体。在这种情况下
 * 并没有单独的内存上下文：CachedPlanSource 结构及
 * 所有附属数据存储在调用者的 CurrentMemoryContext 中，
 * 并且没有办法释放内存，除非清除整个上下文。 
 * 一次性计划始终被视为未保存的。
 *
 * 注意：由 commandTag 引用的字符串不是附属存储；
 * 它被假定为编译时常量字符串。与门户一样，
 * 只有当原始查询字符串（重写前）为空字符串时，
 * commandTag 才应为 NULL。
 */
typedef struct CachedPlanSource
{
	int			magic;			/* 应等于 CACHEDPLANSOURCE_MAGIC */
	struct RawStmt *raw_parse_tree; /* raw_parser() 的输出，或 NULL */
	const char *query_string;	/* 查询的源文本 */
	CommandTag	commandTag;		/* 说得够多了 */
	Oid		   *param_types;	/* 参数类型 OID 的数组，或 NULL */
	int			num_params;		/* param_types 数组的长度 */
	ParserSetupHook parserSetup;	/* 替代参数规格方法 */
	void	   *parserSetupArg;
	int			cursor_options; /* 用于规划的光标选项 */
	bool		fixed_result;	/* 是否不允许更改结果 tupdesc？ */
	TupleDesc	resultDesc;		/* 结果类型；NULL = 不返回元组 */
	MemoryContext context;		/* 保存上述所有内容的内存上下文 */
	/* 这些字段描述当前的分析和重写查询树： */
	List	   *query_list;		/* Query 节点的列表，如果无效则为 NIL */
	List	   *relationOids;	/* 查询依赖的关系的 OID */
	List	   *invalItems;		/* 其他依赖项，作为 PlanInvalItems */
	struct OverrideSearchPath *search_path; /* 用于解析和规划的 search_path */
	MemoryContext query_context;	/* 保存上述内容的上下文，或 NULL */
	Oid			rewriteRoleId;	/* 我们为其进行了重写的角色 ID */
	bool		rewriteRowSecurity; /* 重写期间使用的行安全性 */
	bool		dependsOnRLS;	/* 重写的查询是否特定于上述内容？ */
	/* 如果我们有一个通用计划，则这是对它的引用计数链接： */
	struct CachedPlan *gplan;	/* 通用计划，或 NULL 如果无效 */
	/* 一些状态标志： */
	bool		is_oneshot;		/* 是否为“一次性”计划？ */
	bool		is_complete;	/* CompleteCachedPlan 是否已完成？ */
	bool		is_saved;		/* CachedPlanSource 是否已“保存”？ */
	bool		is_valid;		/* query_list 目前是否有效？ */
	int			generation;		/* 每次创建计划时递增 */
	/* 如果 CachedPlanSource 已保存，则它是一个全局列表的成员 */
	dlist_node	node;			/* list link, if is_saved */
	/* 状态保持以帮助决定是否使用自定义或通用计划: */
	double		generic_cost;	/* 通用计划的成本，如果未知则为-1 */
	double		total_custom_cost;	/* 目前为止自定义计划的总成本 */
	int64		num_custom_plans;	/* 包含在总数中的自定义计划数量 */
	int64		num_generic_plans;	/* 通用计划的数量 */
} CachedPlanSource;

/*
 * CachedPlan 表示从 CachedPlanSource 派生的执行计划。
 * 引用计数包括来自父 CachedPlanSource 的链接（如果有）和任何活动的计划执行，
 * 因此，计划可以在引用计数变为零时被丢弃。结构本身和附属数据都存在于由上下文字段表示的上下文中。
 * 这使得释放不再需要的缓存计划变得容易。 （但是，
 * 如果 is_oneshot 为 true，上下文并不完全属于 CachedPlan，
 * 因此无法释放。）
 */
typedef struct CachedPlan
{
	int			magic;			/* 应等于 CACHEDPLAN_MAGIC */
	List	   *stmt_list;		/* PlannedStmts 的列表 */
	bool		is_oneshot;		/* 是否为“一次性”计划？ */
	bool		is_saved;		/* CachedPlan 是否在长期上下文中？ */
	bool		is_valid;		/* stmt_list 当前是否有效？ */
	Oid			planRoleId;		/* 为其创建该计划的角色 ID */
	bool		dependsOnRole;	/* 该计划是否特定于该角色？ */
	TransactionId saved_xmin;	/* 如果有效，当 TransactionXmin
								 * 从这个值改变时重新规划 */
	int			generation;		/* 该计划的父代生成编号 */
	int			refcount;		/* 对该结构的活动引用计数 */
	MemoryContext context;		/* 包含该 CachedPlan 的上下文 */
} CachedPlan;

/*
 * CachedExpression 是一个低开销的机制，用于缓存独立标量表达式的计划形式。
 * 尽管这样的表达式通常不受缓存失效事件的影响，但这可能会发生，例如由于
 * 替换了内联到表达式中的 SQL 函数。
 * plancache 负责存储表达式树并在发生缓存失效时将其标记为无效，
 * 但调用者必须注意到 !is_valid 状态，并在不重新使用的情况下丢弃过时的表达式。
 * 我们不存储原始解析树，仅存储计划的表达式；
 * 这是基于假设的优化，因为我们通常不希望在会话期间重新规划。
 */
typedef struct CachedExpression
{
	int			magic;			/* 应等于 CACHEDEXPR_MAGIC */
	Node	   *expr;			/* 表达式的计划形式 */
	bool		is_valid;		/* 表达式仍然有效吗？ */
	/* 剩余字段应视为 plancache.c 的私有字段: */
	List	   *relationOids;	/* 表达式依赖的关系的 OID */
	List	   *invalItems;		/* 其他依赖项，作为 PlanInvalItems */
	MemoryContext context;		/* 包含该 CachedExpression 的上下文 */
	dlist_node	node;			/* 在 CachedExpressions 的全局列表中的链接 */
} CachedExpression;


extern void InitPlanCache(void);
extern void ResetPlanCache(void);

extern CachedPlanSource *CreateCachedPlan(struct RawStmt *raw_parse_tree,
										  const char *query_string,
										  CommandTag commandTag);
extern CachedPlanSource *CreateOneShotCachedPlan(struct RawStmt *raw_parse_tree,
												 const char *query_string,
												 CommandTag commandTag);
extern void CompleteCachedPlan(CachedPlanSource *plansource,
							   List *querytree_list,
							   MemoryContext querytree_context,
							   Oid *param_types,
							   int num_params,
							   ParserSetupHook parserSetup,
							   void *parserSetupArg,
							   int cursor_options,
							   bool fixed_result);

extern void SaveCachedPlan(CachedPlanSource *plansource);
extern void DropCachedPlan(CachedPlanSource *plansource);

extern void CachedPlanSetParentContext(CachedPlanSource *plansource,
									   MemoryContext newcontext);

extern CachedPlanSource *CopyCachedPlan(CachedPlanSource *plansource);

extern bool CachedPlanIsValid(CachedPlanSource *plansource);

extern List *CachedPlanGetTargetList(CachedPlanSource *plansource,
									 QueryEnvironment *queryEnv);

extern CachedPlan *GetCachedPlan(CachedPlanSource *plansource,
								 ParamListInfo boundParams,
								 ResourceOwner owner,
								 QueryEnvironment *queryEnv);
extern void ReleaseCachedPlan(CachedPlan *plan, ResourceOwner owner);

extern bool CachedPlanAllowsSimpleValidityCheck(CachedPlanSource *plansource,
												CachedPlan *plan,
												ResourceOwner owner);
extern bool CachedPlanIsSimplyValid(CachedPlanSource *plansource,
									CachedPlan *plan,
									ResourceOwner owner);

extern CachedExpression *GetCachedExpression(Node *expr);
extern void FreeCachedExpression(CachedExpression *cexpr);

#endif							/* PLANCACHE_H */
